package ginplus

import (
	"errors"
	"fmt"
	"gitee.com/hongzhaomin/hzm-common-go/strutil"
	"github.com/gin-gonic/gin"
	"mime/multipart"
	"net/http"
	"reflect"
)

// RestController名称和全路径类名
var _, fullPathRestCtrlName = getTypeName(new(RestController))

// Controller名称和全路径类名
var _, fullPathCtrlName = getTypeName(new(Controller))

// gin.Context名称和全路径类名
var _, fullPathGinCtxName = getTypeName(new(gin.Context))

// multipart.FileHeader名称和全路径类名 - 单文件
var _, fullPathMulFileName = getTypeName(new(multipart.FileHeader))

// multipart.Form名称和全路径类名 - 多文件
var _, fullPathMulFormName = getTypeName(new(multipart.Form))

// IControllerType IController反射类型
var IControllerType = reflect.TypeOf((*iController)(nil)).Elem()

// controller类tag，代表整个controller的统一属性
const (
	mappingTag    = "mapping"
	defaultValTag = "default" // 默认值tag
)

// controller字段tag，这里表示的是方法的属性
const (
	getTag           = "get"    // get请求
	PostTag          = "post"   // post请求
	PutTag           = "put"    // put请求
	deleteTag        = "delete" // delete请求
	funcTag          = "func"   // 映射路径的函数名称tag，即该映射对应的方法名
	requestParamsTag = "args"   // 请求参数的tag，包括路径参数
)

// controllerResolver Controller解析器
type controllerResolver struct {
	ctrlList []iController
}

// 解析Controller
func (cr *controllerResolver) resolveCtrlList() []*HandlerMappingChain {
	ctrlSize := len(cr.ctrlList)
	if ctrlSize <= 0 {
		return nil
	}
	mappingChains := make([]*HandlerMappingChain, 0, ctrlSize)
	for _, ctrl := range cr.ctrlList {
		mappingChain := cr.doResolveSingle(ctrl)
		if mappingChain != nil {
			mappingChains = append(mappingChains, mappingChain)
		}
	}
	return mappingChains
}

func (cr *controllerResolver) doResolveSingle(ic iController) *HandlerMappingChain {
	mustPoint(ic)
	var mappings []*HandlerMapping
	rvIc := reflect.Indirect(reflect.ValueOf(ic))
	rtIc := rvIc.Type()
	if rtIc.Kind() != reflect.Struct {
		return nil
	}

	for i := 0; i < rtIc.NumField(); i++ {
		structField := rtIc.Field(i)
		if structField.Anonymous {
			// 内嵌字段，即继承字段，忽略
			continue
		}

		fieldTag := structField.Tag

		rtField := structField.Type
		if rtField.Kind() == reflect.Ptr {
			rtField = rtField.Elem()
		}
		if rtField.Implements(mappingHandlerRt) {
			mapping := new(HandlerMapping)
			mapping.fieldTag = fieldTag
			switch rtField {
			case getRt:
				mapping.method = http.MethodGet
			case postRt:
				mapping.method = http.MethodPost
			case putRt:
				mapping.method = http.MethodPut
			case deleteRt:
				mapping.method = http.MethodDelete
			default:
				panic(errors.New("不支持的mappingHandler类型"))
			}
			funcName := firstLetter2Upper(structField.Name)
			rvFun := cr.findCtrlMethod(rvIc, funcName)
			mapping.fun = cr.resolveFun(funcName, rvFun, cr.resolveFunParamNames(fieldTag))

			// 解析映射路径
			if url, ok := fieldTag.Lookup(mappingTag); ok {
				mapping.url = url
			}
			mappings = append(mappings, mapping)
		} else {
			if funcName, ok := fieldTag.Lookup(funcTag); ok {
				mapping := new(HandlerMapping)
				mapping.fieldTag = fieldTag
				method, url := cr.resolveUrl(fieldTag)
				mapping.method = method
				mapping.url = url

				rvFun := cr.findCtrlMethod(rvIc, funcName)
				pNames := cr.resolveFunParamNames(fieldTag)
				mapping.fun = cr.resolveFun(funcName, rvFun, pNames)
				mappings = append(mappings, mapping)
			}
		}
	}

	if len(mappings) <= 0 {
		return nil
	}

	mappingChain := cr.resolveControllerTag(ic)
	for _, mapping := range mappings {
		mapping.handlerChain = *mappingChain
	}

	mappingChain.handlerMappings = mappings
	return mappingChain
}

func (cr *controllerResolver) findCtrlMethod(rvIc reflect.Value, funcName string) reflect.Value {
	var rvFun reflect.Value
	rtIc := rvIc.Type()
	ctrlFullName := getFullPathTypeName(rtIc)
	if fun, ok2 := rtIc.MethodByName(funcName); ok2 {
		if strutil.IsNotBlank(fun.PkgPath) {
			msg := fmt.Sprintf("controller[%s]中方法[%s]不可导出", ctrlFullName, funcName)
			panic(msg)
		}
		rvFun = rvIc.MethodByName(funcName)
	} else {
		rvIcPtr := rvIc.Addr()
		fun, ok2 = rvIcPtr.Type().MethodByName(funcName)
		if !ok2 {
			msg := fmt.Sprintf("controller[%s]中找不到方法[%s]", ctrlFullName, funcName)
			panic(msg)
		}
		if strutil.IsNotBlank(fun.PkgPath) {
			msg := fmt.Sprintf("controller[%s]中方法[%s]不可导出", ctrlFullName, funcName)
			panic(msg)
		}
		rvFun = rvIcPtr.MethodByName(funcName)
	}
	return rvFun
}

func (cr *controllerResolver) resolveControllerTag(ic iController) *HandlerMappingChain {
	rvIc := reflect.Indirect(reflect.ValueOf(ic))
	rvIc = reflect.Indirect(rvIc)
	rtIc := rvIc.Type()
	structFields := make([]reflect.StructField, 0, 1)
	for i := 0; i < rtIc.NumField(); i++ {
		rtField := rtIc.Field(i)
		if !rtField.Anonymous {
			continue
		}
		rt := rtField.Type
		if rt.Kind() == reflect.Ptr {
			// 原来不允许是因为后面需要调用父类方法，现在不需要了，允许指针
			//panic(errors.New("内嵌（继承）字段不允许为指针类型"))
			rt = rt.Elem()
		}
		rtFieldFullName := getFullPathTypeName(rt)
		if rtFieldFullName == fullPathRestCtrlName || rtFieldFullName == fullPathCtrlName {
			structFields = append(structFields, rtField)
			continue
		}
		if rt.Implements(IControllerType) || reflect.PointerTo(rt).Implements(IControllerType) {
			// 指针类型或者值类型实现了iController接口，可能是开发者自定义了controller我们也允许
			structFields = append(structFields, rtField)
			continue
		}
	}
	if len(structFields) > 1 {
		panic(errors.New("父类控制器controller能且只能继承一个"))
	}
	rtParent := structFields[0]

	name, pathName := getTypeName(ic)
	mappingChain := &HandlerMappingChain{
		controller:   ic,
		ctrlName:     name,
		ctrlFullName: pathName,
	}
	ctrlType := getCtrlType(getFullPathTypeName(rtParent.Type))
	// 赋值控制器类型
	mappingChain.ctrlType = ctrlType

	parentTag := rtParent.Tag
	mappingChain.fieldTag = parentTag
	if !unknown.is(ctrlType) {
		mappingChain.requestMapping = parentTag.Get(mappingTag)
	}
	return mappingChain
}

func (cr *controllerResolver) resolveUrl(fieldTag reflect.StructTag) (method, url string) {
	existTag := func(requestMethod string) bool {
		_, ok := fieldTag.Lookup(requestMethod)
		return ok
	}
	// 解析http请求类型和url
	switch {
	case existTag(getTag):
		method = http.MethodGet
		url = fieldTag.Get(getTag)
	case existTag(PostTag):
		method = http.MethodPost
		url = fieldTag.Get(PostTag)
	case existTag(PutTag):
		method = http.MethodPut
		url = fieldTag.Get(PutTag)
	case existTag(deleteTag):
		method = http.MethodDelete
		url = fieldTag.Get(deleteTag)
	default:
		method = http.MethodGet
	}
	//mapping.method = method
	//mapping.url = url
	// 解析请求参数名称[requestParamsTag]
	// 如果是路径参数，也需要在该tag中声明参数名
	//if pNameStr, ok := fieldTag.Lookup(requestParamsTag); ok {
	//	pNames = strutil.SpitNotLetterAndNumber(pNameStr)
	//}
	//// 解析权限tag（permissionsTag）
	//if permissionVal, ok := fieldTag.Lookup(permissionsTag); ok {
	//	mapping.permissions = strutil.SpitNotLetterAndNumber(permissionVal)
	//}
	return
}

func (cr *controllerResolver) resolveFunParamNames(fieldTag reflect.StructTag) []string {
	// 解析请求参数名称[requestParamsTag]
	// 如果是路径参数，也需要在该tag中声明参数名
	if pNameStr, ok := fieldTag.Lookup(requestParamsTag); ok {
		return SpitCommaAndTrim(pNameStr)
	}
	return nil
}

// 解析方法结构体类型入参的tag
func (cr *controllerResolver) resolveFunInParamsTag(rtStruct reflect.Type, fieldName string, defaultValMap map[string]string) {
	// 解析默认值tag（default）
	for i := 0; i < rtStruct.NumField(); i++ {
		field := rtStruct.Field(i)
		if strutil.IsNotBlank(field.PkgPath) {
			// 字段不可导出
			continue
		}
		var key string
		if strutil.IsNotBlank(fieldName) {
			key = fieldName + strutil.Dot + field.Name
		} else {
			key = field.Name
		}

		rtField := field.Type
		if rtField.Kind() == reflect.Ptr {
			rtField = rtField.Elem()
		}
		if rtField.Kind() == reflect.Struct {
			cr.resolveFunInParamsTag(rtField, field.Name, defaultValMap)
		}
		if defaultVal, ok := field.Tag.Lookup(defaultValTag); ok {
			defaultValMap[key] = defaultVal
		}
	}
}

func (cr *controllerResolver) resolveFun(funName string, rvFun reflect.Value, pNames []string) HandlerMethod {
	rtFun := rvFun.Type()
	outNum := rtFun.NumOut()
	if outNum > 1 {
		msg := fmt.Sprintf("方法[%s]的返回结果个数不能大于1", funName)
		panic(errors.New(msg))
	}

	inNum := rtFun.NumIn()
	if len(pNames) > inNum {
		msg := fmt.Sprintf("方法[%s]的参数名个数大于参数列表个数", funName)
		panic(errors.New(msg))
	}

	// 利用管道FIFO（先进先出）的特性，将参数名称放入管道，方便后面获取
	pNameChan := make(chan string, 1)
	go putParamName2Chan(pNameChan, pNames)

	funParams := make([]HandlerFunParam, 0)
	for i := 0; i < inNum; i++ {
		var funParam HandlerFunParam
		rtParam := rtFun.In(i)
		// 该方法定义参数是否为指针类型
		definedParamIsPtr := rtParam.Kind() == reflect.Ptr
		if definedParamIsPtr {
			rtParam = rtParam.Elem()
		}
		funParam.definedParamIsPtr = definedParamIsPtr

		// 类型分别判断
		switch rtParam.Kind() {
		case reflect.Struct:
			definedSpecialParamType := getDefinedSpecialParamType(getFullPathTypeName(rtParam))
			// 赋值定义的特殊参数类型
			funParam.definedSpecialParamType = definedSpecialParamType
			if !omit.is(definedSpecialParamType) {
				if !definedParamIsPtr {
					msg := fmt.Sprintf("方法[%s]的[%s]类型参数必须声明为指针类型", funName, rtParam.Name())
					panic(errors.New(msg))
				}
				// 参数类型为gin.Context或者文件
				if mulFile.is(definedSpecialParamType) {
					// 单文件要接收下参数名称
					funParam.pName = <-pNameChan
				}
				break
			}
			// 如果参数为结构体，解析默认值tag，获取所有字段默认值
			defaultValMap := make(map[string]string)
			cr.resolveFunInParamsTag(rtParam, strutil.Empty, defaultValMap)
			funParam.defaultValMap = defaultValMap
		case reflect.Map:
			if definedParamIsPtr {
				msg := fmt.Sprintf("方法[%s]map类型的入参不能声明为指针", funName)
				panic(errors.New(msg))
			}
			if rtParam.Key().Kind() != reflect.String {
				msg := fmt.Sprintf("方法[%s]的map入参'key'类型须定义为'string'", funName)
				panic(errors.New(msg))
			}
			// 判断map类型的value类型
			if rtParam.Elem().Kind() != reflect.String && rtParam.Elem().Kind() != reflect.Slice {
				msg := fmt.Sprintf("方法[%s]的map入参'value'类型须定义为'string/[]string'", funName)
				panic(errors.New(msg))
			}
		case reflect.Slice:
			if definedParamIsPtr {
				msg := fmt.Sprintf("方法[%s]slice类型的入参不能声明为指针", funName)
				panic(errors.New(msg))
			}
			if rtParam.Elem().Kind() == reflect.Struct ||
				rtParam.Elem().Kind() == reflect.Map ||
				rtParam.Elem().Kind() == reflect.Slice ||
				rtParam.Elem().Kind() == reflect.Array ||
				rtParam.Elem().Kind() == reflect.Chan ||
				rtParam.Elem().Kind() == reflect.Func ||
				rtParam.Elem().Kind() == reflect.Interface ||
				rtParam.Elem().Kind() == reflect.UnsafePointer ||
				rtParam.Elem().Kind() == reflect.Ptr {
				panic(errors.New("切片元素数据类型请定义为基本数据类型"))
			}
			funParam.pName = <-pNameChan
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
			reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
			funParam.pName = <-pNameChan
		default:
			msg := fmt.Sprintf("方法[%s]入参无法解析，建议定义为 struct/map[string]string/map[string][]string", funName)
			panic(msg)
		}
		funParam.rtParam = rtParam
		funParams = append(funParams, funParam)
	}
	return HandlerMethod{funName, rvFun, funParams}
}

func putParamName2Chan(pNameChan chan<- string, pNames []string) {
	defer close(pNameChan)
	for _, pName := range pNames {
		pNameChan <- pName
	}
}
